package edu.tongji.structureplayer.core.math
{
    public class Determinant
    {
        protected var _data:Array;
        /**
        * Constructs a standard determinant with j columns and i rows. And all
        * cells are initialised with Number 0.
        * 
        *          j
        *   | x x x ... x |
        *   | x x x ... x |
        *   | : : :     : | i
        *   | : : :     : |
        *   | x x x ... x |
        * 
        */
        public function Determinant(i:uint, j:uint)
        {
            if (i==0 || j==0)
                throw new Error("The dimension can not be zero!");
            
            _data = new Array(i);
            for (var k:uint=0; k<i; k++)
            {
                _data[k] = new Array(j);
                for (var t:uint=0; t<j; t++)
                    _data[k][t] = 0;
            }
        }
        
        public function get rowDimension():uint
        {
            return _data.length;
        }
        
        public function get columnDimension():uint
        {
            return _data[0].length;
        }
        
        public function copy():Determinant
        {
            var det:Determinant = new Determinant(_data.length, _data[0].length);
            for (var i:uint=0; i<_data.length; i++)
            {
                for (var j:uint=0; j<_data[0].length; j++)
                    det.data[i][j] = _data[i][j];
            }
            return det;
        }
        
        /**
        * (X_ik)(Y_kj)=(Z_ij)
        */
        public function multiply(det:Determinant):Determinant
        {
            if (det == null)
                throw new Error("NULL multiplier!");
            if (_data[0].length != det.data.length)
                throw new Error("Invalid multiplier!");
            
            var result:Determinant = new Determinant(_data.length, det.data[0].length);
            for (var i:uint=0; i<_data.length; i++)
            {
                for (var j:uint=0; j<det.data[0].length; j++)
                {
                    result.data[i][j] = 0;
                    for (var k:uint=0; k<det.data.length; k++)
                        result.data[i][j] += _data[i][k]*det.data[k][j];
                }
            }
            return result;
        }
        
        public function add(det:Determinant):Determinant
        {
            if (det == null)
                throw new Error("NULL addend!");
            if (_data.length!=det.data.length || _data[0].length!=det.data[0].length)
                throw new Error("Invalid addend!");
            
            var result:Determinant = this.copy();
            for (var i:uint=0; i<_data.length; i++)
            {
                for (var j:uint=0; j<_data[0].length; j++)
                {
                    result.data[i][j] += det.data[i][j];
                }
            }
            return result;
        }
        
        public function addSelf(det:Determinant):void
        {
            if (det == null)
                throw new Error("NULL addend!");
            if (_data.length!=det.data.length || _data[0].length!=det.data[0].length)
                throw new Error("Invalid addend!");
            
            for (var i:uint=0; i<_data.length; i++)
            {
                for (var j:uint=0; j<_data[0].length; j++)
                {
                    _data[i][j] += det.data[i][j];
                }
            }
        }
        
        public function transpose():Determinant
        {
            var det:Determinant = new Determinant(_data[0].length, _data.length);
            for (var i:uint=0; i<_data.length; i++)
            {
                for (var j:uint=0; j<_data[0].length; j++)
                    det.data[j][i] = _data[i][j];
            }
            return det;
        }
        
        /**
        * Divide current determinant into rowDividend*columnDividend parts equaly.
        * Error occurs when it can not be divided equaly. Returns a two-dimension
        * Array of Determinants whose number of rows is rowDividend and number of
        * columns is columnDividend.
        */
        public function spilt(rowDividend:uint, columnDividend:uint):Array
        {
            if (rowDividend==0 || columnDividend==0)
                throw new Error("Can not divide by zero!");
            if (this.rowDimension%rowDividend!=0 
                || this.columnDimension%columnDimension!=0)
                throw new Error("Can not divide equaly!");
            
            var rows:uint = rowDimension/rowDividend;
            var cols:uint = columnDimension/columnDividend;
            
            var result:Array = new Array(rowDividend);
            for (var i:uint=0; i<result.length; i++)
            {
                result[i] = new Array(columnDividend);
                for (var j:uint=0; j<result[0].length; j++)
                {
                    result[i][j] = new Determinant(rows, cols);
                    for (var ii:uint=0; ii<rows; ii++)
                        for (var jj:uint=0; jj<cols; jj++)
                            result[i][j].data[ii][jj] = 
                                _data[i*rows+ii][j*cols+jj];
                }
            }
            return result;
        }
        
        public function build(source:Array):void
        {
            var rows:uint = (source[0][0] as Determinant).rowDimension;
            var cols:uint = (source[0][0] as Determinant).columnDimension;
            
            if (rows*source.length != this.rowDimension 
                || cols*source[0].length != this.columnDimension)
                throw new Error("Dimension dismatch!");
            
            for (var i:uint=0; i<source.length; i++)
            {
                for (var j:uint=0; j<source[0].length; j++)
                {
                    for (var ii:uint=0; ii<rows; ii++)
                        for (var jj:uint=0; jj<cols; jj++)
                            _data[i*rows+ii][j*cols+jj] = source[i][j].data[ii][jj];
                }
            }
        }
        
        public function debugPrint():void
        {
            for (var i:uint=0; i<_data.length; i++)
            {
                var str:String = new String();
                for (var j:uint=0; j<_data[0].length; j++)
                    str += (_data[i][j]+"\t");
                trace (str);
            }
        }

        public function get data():Array
        {
            return _data;
        }

        public function set data(value:Array):void
        {
            _data = value;
        }

    }
}